package stdchan

import (
	"io"
	"log"
	"net"
	"os"
)

// 不带缓存的 Channels 的使用
// -- 又称为 同步channel
// -- 该程序需要配合 go-test-linux/stdnet/echoserver/Run()或者RunSuffix()工作
func Run() {
	// 无缓存channel会导致【发送者goroutine】的阻塞，直到【接收者goroutine】执行接收。
	// -- 反之亦然，如果【接收者goroutine】先接收的话，也会阻塞，直到【发送者goroutine】执行了发送。
	// ---- 此处有一个 “并发”问题，针对第一种阻塞情况：是接收者先接收到数据呢，还是发送者先被唤醒呢
	// ---- answer: 接收者先接收到数据，然后才唤醒发送者。这种并发内存模型的技术叫做：happens before！！！
	// !!!
	// 此处有一个对于事件并发的定义：x和y事件都发生了，但是我们不能确定这两个发生的先后顺序，那么这两个事件就是并发的。

	// 写一个程序：
	// 1. 连接8000端口
	// 2. 主goroutine从标准输入中读取数据写入连接之中，等待子goroutine接收数据，等待的过程就是用 channel 的阻塞机制。（因为没有接收者，发送的goroutine就会阻塞
	// 3. 子goroutine将连接中的内容写入标准输出，然后接收channel的内容，这样channel的发送者就解除了阻塞
	conn, err := net.Dial("tcp", "localhost:8001") // 8001和8000端口都可以，8001的响应带着前缀[echo]
	if err != nil {
		log.Fatal(err)
	}

	// 创建一个通道，用来测试 接收者的阻塞（只有有人【只能是其他子goroutine】写入通道，才会解除接收者所在goroutine的阻塞状态）
	done := make(chan struct{}) // 通道的类型设置为空结构体，这个类型不会占内存空间!!!

	// 子goroutine接收连接中的内容，并且解除主goroutine的阻塞，通过写入通道
	go func() {
		log.Println("[sub gorutine]...")
		io.Copy(os.Stdout, conn) // 如果我们连接的是回声服务器，那么主goroutine向tcp连接中写入的所有东西都会在此处echo出来
		log.Println("[sub gorutine]done")
		done <- struct{}{}
	}()

	// 从标准输入中读入内容，写入到连接到8000端口的连接之中
	log.Println("[main gorutine]prepare to read stdin. CTRL+D will end the input.")
	io.Copy(conn, os.Stdin) // 在pty上输入 CTRL+D 也就是通过标准输入写入一个 EOF 从而使得io.Copy终止拷贝操作，继续下方操作
	log.Println("[main gorutine]read stdin done")
	//conn.Close()

	tcpConn, ok := conn.(*net.TCPConn)
	if !ok {
		log.Println("[main gorutine]close connection with read and write")
		conn.Close() // 如果是连接的循环输出的8001服务端，一旦此处close掉了，服务端的输出循环就被中断了。因为这个close操作会在socket的结构中标识读端被关闭，此时如果服务端继续发送数据，会收到客户端的RST标识的TCP响应。
	} else {
		log.Println("[main gorutine]close tcp connection with write")
		tcpConn.CloseWrite() //  如果是连接的循环输出的8001服务端，此处closeWrite并不会影响服务端继续完成循环的输出
	}

	<-done // 只要没人向该通道中发送，就阻塞在此处
	log.Println("[main gorutine]done")
}
